<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>属性的可枚举性和遍历</title>
</head>
<body>
  <script>
    // 对象的每个属性，都有个描述对象(Descriptor),用来控制该属性的行为。
    // Object.getOwnPropertyDescriptor方法可以获取该属性的描述对象。
    var obj = { foo: 123};
    var descriptor = Object.getOwnPropertyDescriptor(obj, 'foo');
    console.log(descriptor);
    //  {
    //    configurable: true
    //    enumerable: true, // 可枚举值，如果该属性为false,表示某些操作会忽略当前属性。
    //    value: 123,
    //    writable: true
    //  }
    /**
     * 目前有四个操作会忽略enumerable为false的属性。
     * --for...in循环：只遍历对象自身的和继承的可枚举值的属性。
     * --Object.keys(): 返回对象自身的所有可枚举的属性名的键名。
     * --JSON.stringify(): 只串行化对象自身的可枚举的属性。
     * --Object.assign(): 只拷贝对象自身的可枚举的属性。
     *
     * 这四个操作中，前三个是es5就有的，，最后一个是es6新增的。其中，只有for...in循环会返回继承的属性。
     * 其他三个方法都会忽略继续的属性，只处理对象自身的属性。实际上，引入“可枚举”这个概念的最初目的。
     * 就是让某些属性可以规避掉for...in操作。不然所有内部属性和方法都会被遍历到。
     * 比如，对象原型的toString方法，以及数组的length属性，就通过“可枚举性”，从而避免被for...in遍历到。
    */
    var a = Object.getOwnPropertyDescriptor(Object.prototype, 'toString').enumerable
    console.log(a)//false
    var b = Object.getOwnPropertyDescriptor([], 'length').enumerable
    console.log(b)// false
    // 上面代码中，toString和length属性的enumerable都是false,因此for...in不会遍历到这俩个继承自原型的属性。
    // 另外，es6规定，所有Class的原型的方法都是不可枚举的。
    var c = Object.getOwnPropertyDescriptor(class {foo() {}}.prototype, 'foo').enumerable
    console.log(c)// false
    // 总的来说，操作中引入继承的属性会让问题复杂化，大多数时候，我们只关心对象自身的属性。
    // 所以，尽量不要用for...in循环，而用Object.keys()代替。
  </script>
  <script>
    // 属性的遍历，es6一共有5种方法可以遍历对象的属性。
    /**
     * (1)for...in：循环遍历对象自身的和继承的可枚举属性(不含Symbol属性)。
     * (2)Object.keys(obj): Object.keys返回一个数组，包括对象自身的(不含继承的)所有可枚举属性(不含Symbol属性)的键名，
     * (3)Object.getOwnPropertyNames(obj): 返回一个数组，包含对象自身的所有属性(不含Symbol属性，但是包括不可枚举属性)的键名。
     * (4)Object.getOwnPropertySymbols(obj): Object.getOwnPropertySymbols返回一个数组。包含对象自身的所有Symbol属性的键名。
     * (5)Reflect.ownKeys(obj): Reflect.ownKeys返回一个数组，包含对象自身的(不含继承的)所有键名。不管键名是Symbol或字符串，
     * 也不管是否可枚举，都会返回到那个数组中。
     *
     * 以上的5种方法遍历对象的键名，都遵守同样的属性遍历的次序规则
     *
     * --首先遍历所有数值键，按照数值升序排列。
     * --其次遍历所有字符串键，按照加入时间升序排列。
     * --最后遍历所有 Symbol 键，按照加入时间升序排列。
    */
    Reflect.ownKeys({ [Symbol()]:0, b:0, 10:0, 2:0, a:0 });
    // ['2', '10', 'b', 'a', Symbol()]
    //上面代码中，Reflect.ownKeys方法返回一个数组，包含了参数对象的所有属性。
    // 这个数组的属性次序是这样的，首先是数值属性2和10，其次是字符串属性b和a，最后是 Symbol 属性。
  </script>
</body>
</html>